package com.masu.knows.auth.security;

import com.masu.knows.auth.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import java.util.Arrays;

/**
 * @author susi
 * @date 2022/5/18
 */
@Configuration
// 这个注解表示下面的类是配置Oauth2授权标准的核心类
// Oauth2包含的控制器方法中会读取下面的配置,实施具体授权
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    // 配置授权服务器的登录参数(配置用什么方式获取授权)
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //endpoints就是端点的意思,这里指客户端登录访问的Rest接口
        //  配置SpringSecurity中的认证管理器
        endpoints.authenticationManager(authenticationManager)
                //配置登录获得用户详情的方法
                .userDetailsService(userDetailsService)
                // 设置登录的方式只能是post保证安全
                .allowedTokenEndpointRequestMethods(HttpMethod.POST)
                // 配置令牌生成器(如何生成令牌)
                .tokenServices(tokenService());
    }

    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;

    // 配置生成令牌的方法
    @Bean
    public AuthorizationServerTokenServices tokenService() {
        //下面开始设置生成令牌的参数
        DefaultTokenServices services = new DefaultTokenServices();
        //支持令牌刷新
        services.setSupportRefreshToken(true);
        //设置令牌生成对象
        services.setTokenStore(tokenStore);
        // ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
        // 必须设置令牌增强才能生成Jwt令牌
        // 实例化令牌增强对象
        TokenEnhancerChain chain=new TokenEnhancerChain();
        // 令牌增强对象可以设置很转换器,我们Jwt转换器是其中一个
        // 将Jwt转换器添加到这个令牌增强对象中
        chain.setTokenEnhancers(
                Arrays.asList(accessTokenConverter));
        // 将令牌增强对象赋值为令牌生成器
        services.setTokenEnhancer(chain);
        // ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
        // 设置令牌有效期(3600是秒 指1小时有效)
        services.setAccessTokenValiditySeconds(3600);
        // 设置令牌刷新最大时间
        services.setRefreshTokenValiditySeconds(3600);
        // 配置客户端详情
        services.setClientDetailsService(clientDetailsService);
        // 千万别忘了返回services
        return services;
    }

    @Autowired
    private PasswordEncoder passwordEncoder;
    // 配置客户端详情,规定客户端权限
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()  //在内存中保存客户端权限设置
                //设置客户端id
                .withClient("knows")
                //当前客户端加密规则:设置一个秘钥
                .secret(passwordEncoder.encode("123456"))
                //赋予客户端权限(只是一个名称)
                .scopes("main")
                //  允许客户端登录的类型
                //  password表示支持用户名密码登录
                //  refresh_token表示支持令牌刷新
                .authorizedGrantTypes("password","refresh_token");
    }
    // 认证成功后,当前客户端能够访问Oauth2的端点设置
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                // 当前Oauth2允许任何人访问/oauth/token端点
                .tokenKeyAccess("permitAll()")
                // 当前Oauth2允许任何人访问/oauth/check_token端点
                .checkTokenAccess("permitAll()")
                // 允许通过验证的客户端获得令牌
                .allowFormAuthenticationForClients();
    }
}
